package gov.nih.nlm.lowlinc.csv;

import gov.nih.nlm.lowlinc.accessionwrapper.LOINCID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCPartID;
import gov.nih.nlm.lowlinc.accessionwrapper.LOINCTermID;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class AxialGraph {

	private final Map<LOINCID, AxialNode<LOINCID>> nodes = new HashMap<>();
	private final Set<LOINCPartID> parts = new HashSet<>();
	private final Set<LOINCTermID> terms = new HashSet<>();

	public void addParentChild(LOINCID parentID, LOINCID childID) {
		AxialNode<LOINCID> parentNode = getNode(parentID);
		AxialNode<LOINCID> childNode = getNode(childID);
		parentNode.addChild(childID);
		childNode.addParent(parentID);
	}

	public Set<LOINCPartID> getParts() {
		return parts;
	}

	public Set<LOINCTermID> getTerms() {
		return terms;
	}

	public Set<LOINCID> getNodes() {
		return nodes.keySet();
	}

	public Set<LOINCID> getChildren(LOINCID id) {
		AxialNode<LOINCID> node = getNode(id);
		return node.getChildren();
	}

	public Set<LOINCID> getParents(LOINCID id) {
		AxialNode<LOINCID> node = getNode(id);
		return node.getParents();
	}

	public Set<LOINCID> getAllParents(LOINCID id) {
		HashSet<LOINCID> result = new HashSet<>();

		AxialNode<LOINCID> node = getNode(id);
		for (LOINCID parentID : node.getParents()) {
			result.add(parentID);
			result.addAll(getAllParents(parentID));
		}

		return result;
	}

	// delme unecessary step in recurrance
	// private Set<LOINCID> getAllParents(LOINCID id, HashSet<LOINCID> result) {
	// AxialNode<LOINCID> node = getNode(id);
	// for (LOINCID parentID : node.getParents()) {
	// result.add(parentID);
	// getAllParents(parentID, result);
	// }
	// return result;
	// }

	// note this will create the node if not found
	private AxialNode<LOINCID> getNode(LOINCID id) {
		AxialNode<LOINCID> node = nodes.get(id);
		if (node == null) {
			node = new AxialNode<LOINCID>(id);
			nodes.put(id, node);
			if (id instanceof LOINCPartID) {
				parts.add((LOINCPartID) id);
			} else if (id instanceof LOINCTermID
					&& !id.getID().contains("OBS_")) {
				terms.add((LOINCTermID) id);
			}
		}
		return node;
	}

	private class AxialNode<T> {
		private final Set<T> parents = new HashSet<>();
		private final Set<T> children = new HashSet<>();

		T accession;

		AxialNode(T loincid) {
			accession = loincid;
		}

		public void addChild(T id) {
			children.add(id);
		}

		public void addParent(T id) {
			parents.add(id);
		}

		public Set<T> getChildren() {
			return children;
		}

		public Set<T> getParents() {
			return parents;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + getOuterType().hashCode();
			result = prime * result
					+ (accession == null ? 0 : accession.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			}
			if (obj == null) {
				return false;
			}
			if (getClass() != obj.getClass()) {
				return false;
			}
			AxialNode<?> other = (AxialNode<?>) obj;
			if (!getOuterType().equals(other.getOuterType())) {
				return false;
			}
			if (accession == null) {
				if (other.accession != null) {
					return false;
				}
			} else if (!accession.equals(other.accession)) {
				return false;
			}
			return true;
		}

		private AxialGraph getOuterType() {
			return AxialGraph.this;
		}

	}

}
